''' State.py

	State data classes
	
	
   Copyright 2008 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
'''

from struct import *
import sys

class ExtensionType(object):
	None_				= 0x00
	Nunchuk				= 0xfe
	ClassicController	= 0xfd

class IRMode(object):
	Off					= 0x00
	Basic				= 0x01
	Extended			= 0x03
	Full				= 0x05

# convert fixed point to float
def QF(value, n):
	return float(value) / 2**n

class ButtonState(object):
	def __init__(self):
		self.A = False
		self.B = False
		self.Minus = False
		self.Home = False
		self.Plus = False
		self.One = False
		self.Two = False
		self.Up = False
		self.Down = False
		self.Left = False
		self.Right = False
	
	def __str__(self):
		buttons = (
			(self.A, 'A'),
			(self.B, 'B'),
			(self.Minus, '-'),
			(self.Home, 'H'),
			(self.Plus, '+'),
			(self.One, '1'),
			(self.Two, '2'),
			(self.Up, 'U'),
			(self.Down, 'D'),
			(self.Left, 'L'),
			(self.Right, 'R')
		)
		return 'Buttons: ' + ' '.join([label for on, label in buttons if on])
	
	def update(self, data):
		buttons, = unpack('<H', data[1:3])
		self.A = (buttons & 0x0800) != 0
		self.B = (buttons & 0x0400) != 0
		self.Minus = (buttons & 0x1000) != 0
		self.Home = (buttons & 0x8000) != 0
		self.Plus = (buttons & 0x0010) != 0
		self.One = (buttons & 0x0200) != 0
		self.Two = (buttons & 0x0100) != 0
		self.Up = (buttons & 0x0008) != 0
		self.Down = (buttons & 0x0004) != 0
		self.Left = (buttons & 0x0001) != 0
		self.Right = (buttons & 0x0002) != 0

class AccelState(object):
	def __init__(self):
		self.RawX = 0
		self.RawY = 0
		self.RawZ = 0
		self.X = 0
		self.Y = 0
		self.Z = 0
	
	def __str__(self):
		return '(% .2f, % .2f, % .2f) Raw(%3d, %3d, %3d)' % (self.X, self.Y, self.Z,
			self.RawX, self.RawY, self.RawZ)
	
	def update(self, data, calibration):
		self.RawX, self.RawY, self.RawZ = unpack('>BBB', data[3:6])
		
		try:
			self.X = (float(self.RawX) - float(calibration.X0)) \
				/ (float(calibration.XG) - float(calibration.X0))
			self.Y = (float(self.RawY) - float(calibration.Y0)) \
				/ (float(calibration.YG) - float(calibration.Y0))
			self.Z = (float(self.RawZ) - float(calibration.Z0)) \
				/ (float(calibration.ZG) - float(calibration.Z0))
		except:
			pass

class AccelCalibrationInfo(object):
	def __init__(self):
		self.X0 = 0
		self.Y0 = 0
		self.Z0 = 0
		self.XG = 0
		self.YG = 0
		self.ZG = 0
	
	def __str__(self):
		return '0(%3d, %3d, %3d) G(%3d, %3d, %3d)' % (self.X0, self.Y0, self.Z0,
			self.XG, self.YG, self.GZ)
	
	def update(self, data):
		self.X0, self.Y0, self.Z0, self.XG, self.YG, self.GZ = unpack('>3Bx3B', data[0:7])

class NunchukCalibrationInfo(object):
	def __init__(self):
		self.AccelCalibrationInfo = AccelCalibrationInfo()
		self.MinX = 0
		self.MidX = 0
		self.MaxX = 0
		self.MinY = 0
		self.MidY = 0
		self.MaxY = 0
	
	def __str__(self):
		return 'Nunchuk: Min(%3d, %3d) Mid(%3d, %3d) Max(%3d, %3d) - %s' % (
			self.MinX, self.MinY, self.MidX, self.MidY, self.MaxX, self.MaxY,
			str(self.AccelCalibrationInfo)
		)
	
	def update(self, data):
		self.AccelCalibrationInfo.update(data)
		self.MaxX, self.MinX, self.MidX, self.MaxY, self.MinY, self.MidY = unpack('>3B3B', data[8:14])

class ClassicControllerCalibrationInfo(object):
	def __init__(self):
		self.MinXL = 0
		self.MidXL = 0
		self.MaxXL = 0
		self.MinYL = 0
		self.MidYL = 0
		self.MaxYL = 0
		self.MinXR = 0
		self.MidXR = 0
		self.MaxXR = 0
		self.MinYR = 0
		self.MidYR = 0
		self.MaxYR = 0
		self.MinTriggerL = 0
		self.MaxTriggerL = 0
		self.MinTriggerR = 0
		self.MaxTriggerR = 0
	
	def __str__(self):
		return 'Classic: Min L(%s, %s) R(%s, %s) Mid L(%s, %s) R(%s, %s) Max L(%s, %s) R(%s, %s) Tl(%s - %s) Tr(%s - %s)' % (
			self.MinXL, self.MinYL, self.MinXR, self.MinYR, self.MidXL, self.MidYL, self.MidXR, self.MidYR,
			self.MaxXL, self.MaxYL, self.MaxXR, self.MaxYR, self.MinTriggerL, self.MaxTriggerL,
			self.MinTriggerR, self.MaxTriggerR
		)
	
	def update(self, data):
		idata = unpack('>16B', data[0:16])
		
		self.MaxXL = QF(idata[0], 2)
		self.MinXL = QF(idata[1], 2)
		self.MidXL = QF(idata[2], 2)
		self.MaxYL = QF(idata[3], 2)
		self.MinYL = QF(idata[4], 2)
		self.MidYL = QF(idata[5], 2)
		self.MaxXR = QF(idata[6], 3)
		self.MinXR = QF(idata[7], 3)
		self.MidXR = QF(idata[8], 3)
		self.MaxYR = QF(idata[9], 3)
		self.MinYR = QF(idata[10], 3)
		self.MidYR = QF(idata[11], 3)
		self.MinTriggerL = 0
		self.MinTriggerR = 0
		self.MaxTriggerL = 31
		self.MaxTriggerR = 31

class NunchukState(object):
	def __init__(self):
		self.CalibrationInfo = NunchukCalibrationInfo()
		self.AccelState = AccelState()
		self.RawX = 0
		self.RawY = 0
		self.X = 0
		self.Y = 0
		self.C = False
		self.Z = False
	
	def __str__(self):
		buttons = (
			(self.C, 'C'),
			(self.Z, 'Z'),
		)
		cz = ''.join([label for on, label in buttons if on])
		
		return 'Nunchuk: (% .2f, % .2f) % 2s | %s' % (self.X, self.Y, cz, self.AccelState)
	
	def update(self, data):
		self.RawX, self.RawY, CZ = unpack('>BBxxxB', data[0:6])
		self.C = (CZ & 0x02) == 0
		self.Z = (CZ & 0x01) == 0
		self.AccelState.update('\x00\x00\x00' + data[2:5], self.CalibrationInfo.AccelCalibrationInfo)
		
		try:
			if self.CalibrationInfo.MaxX != 0:
				self.X = float(self.RawX - self.CalibrationInfo.MidX) \
					/ float(self.CalibrationInfo.MaxX - self.CalibrationInfo.MinX)
			if self.CalibrationInfo.MaxY != 0:
				self.Y = float(self.RawY - self.CalibrationInfo.MidY) \
					/ float(self.CalibrationInfo.MaxY - self.CalibrationInfo.MinY)
		except:
			pass

class ClassicControllerButtonState(object):
	def __init__(self):
		self.A = False
		self.B = False
		self.Plus = False
		self.Home = False
		self.Minus = False
		self.Up = False
		self.Down = False
		self.Left = False
		self.Right = False
		self.X = False
		self.Y = False
		self.ZL = False
		self.ZR = False
		self.TriggerL = False
		self.TriggerR = False
	
	def __str__(self):
		buttons = (
			(self.A, 'A'),
			(self.B, 'B'),
			(self.Plus, '+'),
			(self.Home, 'H'),
			(self.Minus, '-'),
			(self.Up, 'U'),
			(self.Down, 'D'),
			(self.Left, 'L'),
			(self.Right, 'R'),
			(self.X, 'X'),
			(self.Y, 'Y'),
			(self.ZL, 'Zl'),
			(self.ZR, 'Zr'),
			(self.TriggerL, 'Tl'),
			(self.TriggerR, 'Tr'),
		)
		
		return ''.join([label for on, label in buttons if on])
	
	def update(self, data):
		idata = unpack('>2B', data[0:2])
		
		self.TriggerR = (idata[0] & 0x02) == 0
		self.Plus = (idata[0] & 0x04) == 0
		self.Home = (idata[0] & 0x08) == 0
		self.Minus = (idata[0] & 0x10) == 0
		self.TriggerL = (idata[0] & 0x20) == 0
		self.Down = (idata[0] & 0x40) == 0
		self.Right = (idata[0] & 0x80) == 0
		self.Up = (idata[1] & 0x01) == 0
		self.Left = (idata[1] & 0x02) == 0
		self.ZR = (idata[1] & 0x04) == 0
		self.X = (idata[1] & 0x08) == 0
		self.A = (idata[1] & 0x10) == 0
		self.Y = (idata[1] & 0x20) == 0
		self.B = (idata[1] & 0x40) == 0
		self.ZL = (idata[1] & 0x80) == 0

class ClassicControllerState(object):
	def __init__(self):
		self.CalibrationInfo = ClassicControllerCalibrationInfo()
		self.ButtonState = ClassicControllerButtonState()
		self.RawXL = 0
		self.RawYL = 0
		self.RawXR = 0
		self.RawYR = 0
		self.XL = 0.0
		self.YL = 0.0
		self.XR = 0.0
		self.YR = 0.0
		self.RawTriggerL = 0
		self.RawTriggerR = 0
		self.TriggerL = 0.0
		self.TriggerR = 0.0
	
	def __str__(self):
		return 'Classic Controller: L(% .2f, % .2f|% .2f) R(% .2f, % .2f|% .2f) %s' % (
			self.XL, self.YL, self.TriggerL, self.XR, self.YR, self.TriggerR,
			self.ButtonState
		)
	
	def update(self, data):
		idata = unpack('>4B', data[0:4])
		
		self.RawXL = idata[0] & 0x3f
		self.RawYL = idata[1] & 0x3f
		self.RawXR = (idata[2] >> 7) | ((idata[1] & 0xc0) >> 5) | ((idata[0] & 0xc0) >> 3)
		self.RawYR = idata[2] & 0x1f
		self.RawTriggerL = ((idata[2] & 0x60) >> 2) | (idata[3] >> 5)
		self.RawTriggerR = idata[3] & 0x1f
		
		self.ButtonState.update(data[4:6])
		
		try:
			if self.CalibrationInfo.MaxXL != 0x00:
				self.XL = float(self.RawXL - self.CalibrationInfo.MidXL) \
					/ float(self.CalibrationInfo.MaxXL - self.CalibrationInfo.MinXL)
			if self.CalibrationInfo.MaxYL != 0x00:
				self.YL = float(self.RawYL - self.CalibrationInfo.MidYL) \
					/ float(self.CalibrationInfo.MaxYL - self.CalibrationInfo.MinYL)
			if self.CalibrationInfo.MaxXR != 0x00:
				self.XR = float(self.RawXR - self.CalibrationInfo.MidXR) \
					/ float(self.CalibrationInfo.MaxXR - self.CalibrationInfo.MinXR)
			if self.CalibrationInfo.MaxYR != 0x00:
				self.YR = float(self.RawYR - self.CalibrationInfo.MidYR) \
					/ float(self.CalibrationInfo.MaxYR - self.CalibrationInfo.MinYR)
			
			if self.CalibrationInfo.MaxTriggerL != 0x00:
				self.TriggerL = float(self.RawTriggerL) \
					/ float(self.CalibrationInfo.MaxTriggerL - self.CalibrationInfo.MinTriggerL)
			if self.CalibrationInfo.MaxTriggerR != 0x00:
				self.TriggerR = float(self.RawTriggerR) \
					/ float(self.CalibrationInfo.MaxTriggerR - self.CalibrationInfo.MinTriggerR)
		except:
			print str(sys.exc_value)

class IRState(object):
	def __init__(self):
		self.Mode = IRMode.Off
		self.RawX1 = 0
		self.RawX2 = 0
		self.RawX3 = 0
		self.RawX4 = 0
		self.RawY1 = 0
		self.RawY2 = 0
		self.RawY3 = 0
		self.RawY4 = 0
		self.Size1 = 0
		self.Size2 = 0
		self.Size3 = 0
		self.Size4 = 0
		self.Found1 = False
		self.Found2 = False
		self.Found3 = False
		self.Found4 = False
		self.X1 = 0.0
		self.X2 = 0.0
		self.X3 = 0.0
		self.X4 = 0.0
		self.Y1 = 0.0
		self.Y2 = 0.0
		self.Y3 = 0.0
		self.Y4 = 0.0
	
	def __str__(self):
		return 'IR: %d1(% .2f, % .2f|%x) %d2(% .2f, % .2f|%x)%d 3(% .2f, % .2f|%x) %d4(% .2f, % .2f|%x)' % (
			int(self.Found1), self.X1, self.Y1, self.Size1,
			int(self.Found2), self.X2, self.Y2, self.Size2,
			int(self.Found3), self.X3, self.Y3, self.Size3,
			int(self.Found4), self.X4, self.Y4, self.Size4
		)
	
	def update(self, data):
		try:
			idata = unpack('>22B', data)
			
			self.RawX1 = idata[6] | ((idata[8] >> 4) & 0x03) << 8
			self.RawY1 = idata[7] | ((idata[8] >> 6) & 0x03) << 8
			
			if self.Mode == IRMode.Basic:
				self.RawX2 = idata[9] | ((idata[8] >> 0) & 0x03) << 8
				self.RawY2 = idata[10] | ((idata[8] >> 2) & 0x03) << 8
				
				self.Size1 = 0x00
				self.Size2 = 0x00
				
				self.Found1 = not (idata[6] == 0xff and idata[7] == 0xff)
				self.Found2 = not (idata[9] == 0xff and idata[10] == 0xff)
			elif self.Mode == IRMode.Extended:
				self.RawX2 = idata[9]  | ((idata[11] >> 4) & 0x03) << 8
				self.RawY2 = idata[10] | ((idata[11] >> 6) & 0x03) << 8
				
				self.Size1 = idata[8] & 0x0f
				self.Size2 = idata[11] & 0x0f
				
				self.Found1 = not (idata[6] == 0xff and idata[7] == 0xff and idata[8] == 0xff)
				self.Found2 = not (idata[9] == 0xff and idata[10] == 0xff and idata[11] == 0xff)
				
				self.RawX3 = idata[12] | ((idata[14] >> 4) & 0x03) << 8
				self.RawY3 = idata[13] | ((idata[14] >> 6) & 0x03) << 8
				self.Size3 = idata[14] & 0x0f;
				self.Found3 = not (idata[12] == 0xff and idata[13] == 0xff and idata[14] == 0xff)
				
				self.RawX4 = idata[15] | ((idata[17] >> 4) & 0x03) << 8
				self.RawY4 = idata[16] | ((idata[17] >> 6) & 0x03) << 8
				self.Size4 = idata[17] & 0x0f
				self.Found4 = not (idata[15] == 0xff and idata[16] == 0xff and idata[17] == 0xff)
			elif self.Mode == IRMode.Full:
				pass
			
			self.X1 = float(self.RawX1) / 1023.5
			self.X2 = float(self.RawX2) / 1023.5
			self.X3 = float(self.RawX3) / 1023.5
			self.X4 = float(self.RawX4) / 1023.5
			self.Y1 = float(self.RawY1) / 767.5
			self.Y2 = float(self.RawY2) / 767.5
			self.Y3 = float(self.RawY3) / 767.5
			self.Y4 = float(self.RawY4) / 767.5
		except:
			print 'IRState: %s' % str(sys.exc_value)

class LEDs(object):
	def __init__(self):
		self.LED1 = False
		self.LED2 = False
		self.LED3 = False
		self.LED4 = False
	
	def __str__(self):
		return 'LEDs: %d %d %d %d' % (int(self.LED1), int(self.LED2), int(self.LED3), int(self.LED4))
	
	def update(self, led1, led2, led3, led4):
		self.LED1, self.LED2, self.LED3, self.LED4 = led1, led2, led3, led4

class WiimoteState(object):
	def __init__(self):
		self.CalibrationInfo = AccelCalibrationInfo()
		self.ButtonState = ButtonState()
		self.AccelState = AccelState()
		self.IRState = IRState()
		self.Battery = 0
		self.Rumble = False
		self.Extension = False
		self.ExtensionType = ExtensionType.None_
		self.NunchukState = NunchukState()
		self.ClassicControllerState = ClassicControllerState()
		self.LEDs = LEDs()
